
import { ConnectionInputOperate, GameSyncFrame } from "../../shared/gameClient/GameSyncFrame";
import { MsgAfterFrames } from "../../shared/gameClient/protocols/MsgAfterFrames";
import { MsgInpFrame } from "../../shared/gameClient/protocols/MsgInpFrame";
import { MsgRequireSyncState } from "../../shared/gameClient/protocols/MsgRequireSyncState";
import { MsgSyncFrame } from "../../shared/gameClient/protocols/MsgSyncFrame";
import { MsgSyncState } from "../../shared/gameClient/protocols/MsgSyncState";

export interface InputHandler {
    [key: string]: ((connId: string, inputFrame: ConnectionInputOperate, dt: number) => void);
}

export interface IFrameSyncConnect {
    onAfterFrames: (msg: MsgAfterFrames) => void;
    onSyncFrame: (msg: MsgSyncFrame) => void;
    onRequireSyncState: (msg: MsgRequireSyncState) => void;
    sendSyncState(msg: MsgSyncState): void;
    sendInpFrame(msg: MsgInpFrame): void;
    disconnect(): void;
}

/**帧同步执行器,对接帧同步的实现*/
export class FrameSyncExecutor {

    inputHandler: any;
    serverSyncFrameRate = 60;
    renderFrameInvMs = 1000 / this.serverSyncFrameRate;
    renderFrameDt = 1 / this.serverSyncFrameRate;
    /**追帧列表, 索引0的帧索引值 = stateFrameIndex + 1 */
    afterFrames: GameSyncFrame[] = [];
    stateData: any = null;
    /**状态同步数据是在本帧索引的数据, 即追帧是从本帧索引+1开始*/
    stateFrameIndex = -1;
    /**当前执行到的帧索引(已执行)*/
    executeFrameIndex = -1;
    /**最大可执行帧索引*/
    maxCanRenderFrameIndex = -1;
    /**执行帧已经停止*/
    executeFrameStop = true;
    executeNextFrameHandler: any;
    executeNextFrameTimerHD: any = 0;
    connect: IFrameSyncConnect;

    public onSyncStateData: (stateData: any, stateFrameIndex: number) => void;
    public onExecFrame: (dt: number, frameIndex: number) => void;
    public getSyncState?: () => any;

    /**
     * 
     * @date 2022/2/21 - 下午3:43:43
     *
     * @constructor
     * @param inputHandler 输入帧处理器,实现函数: execInput_<操作类型>(connId: string, inputFrame: ConnectionInputOperate, dt: number)
     * @param onSyncStateData 触发追帧时,需要同步状态数据,可以当作重新初始化游戏数据,一般只发生在刚连上服务器或断线重连成功时
     * @param onExecFrame 执行每一帧的回调,数据帧和渲染帧要区分开来
     * @param getSyncState 当要求状态同步时,需要返回当前状态数据, 方便快速追帧, 不传表示禁用本功能
     */
    constructor(connect: IFrameSyncConnect, inputHandler: any,
        onSyncStateData: (stateData: any, stateFrameIndex: number) => void, onExecFrame: (dt: number, frameIndex: number) => void, getSyncState?: () => any) {
        this.connect = connect;
        this.inputHandler = inputHandler;
        this.onSyncStateData = onSyncStateData;
        this.onExecFrame = onExecFrame;
        this.getSyncState = getSyncState;
        this.executeNextFrameHandler = this.executeNextFrame.bind(this);

        this.connect.onAfterFrames = (msg) => {
            this.onMsgAfterFrames(msg);
        };
        this.connect.onSyncFrame = (msg) => {
            this.onSyncFrame(msg);
        };
        this.connect.onRequireSyncState = (msg) => {
            if (this.getSyncState) {
                const stateFrameIndex = this.executeFrameIndex;
                const stateInArrIndex = this.getAfterFramesArrIndex(stateFrameIndex);
                this.stateData = this.getSyncState();
                this.stateFrameIndex = stateFrameIndex;
                console.log('getSyncState',
                    `afterFramesLen:${this.afterFrames.length}, arrIndexEnd:${stateInArrIndex}, stateFrameIndex:${stateFrameIndex}, stateData:`,
                    JSON.parse(JSON.stringify(this.stateData)));
                if (this.afterFrames.length > stateInArrIndex) this.afterFrames.splice(0, stateInArrIndex + 1);
                this.connect.sendSyncState({
                    stateData: this.stateData,
                    stateFrameIndex: this.stateFrameIndex,
                });
            }
        };
    }
    public async dispose(): Promise<void> {
        this.stopExecuteFrame();
        await this.connect.disconnect();
    }

    /**当服务端要求追帧时触发, 更新相关数据*/
    onMsgAfterFrames(msg: MsgAfterFrames) {

        console.log("onMsgAfterFrames", JSON.parse(JSON.stringify(msg)));

        this.serverSyncFrameRate = msg.serverSyncFrameRate;
        this.renderFrameInvMs = 1000 / this.serverSyncFrameRate;
        this.renderFrameDt = 1 / this.serverSyncFrameRate;
        this.afterFrames = msg.afterFrames;
        this.stateData = msg.stateData;
        this.stateFrameIndex = msg.stateFrameIndex;
        this.maxCanRenderFrameIndex = msg.maxSyncFrameIndex;
        this.executeFrameIndex = this.stateFrameIndex;

        this.onSyncStateData?.call(this, this.stateData, this.stateFrameIndex);

        if (this.executeFrameStop) {
            //如果执行已经停下来,则开始执行
            this.executeNextFrame();
        }
    }
    /**收到服务端的一帧, 会推在帧队列里, 并做一些数据校验*/
    onSyncFrame(frame: MsgSyncFrame) {
        const arrIndex = this.getAfterFramesArrIndex(frame.frameIndex);
        this.afterFrames[arrIndex] = frame.syncFrame;

        if (this.maxCanRenderFrameIndex + 1 == frame.frameIndex) {
            //同步下来的帧,是按顺序的,则更新最大可执行帧索引, 不按顺序的只插入,但不更新最大可渲染帧索引,直到按顺序的那个
            this.maxCanRenderFrameIndex = frame.frameIndex;
            //如果期间有 没按顺序先来的后续帧, 则最大可渲染帧索引 推到最后一个不为空的帧
            for (let fi = this.maxCanRenderFrameIndex + 1, i = this.getAfterFramesArrIndex(fi);
                i < this.afterFrames.length; fi++, i++) {
                if (this.afterFrames[i]) {
                    this.maxCanRenderFrameIndex = fi;
                } else {
                    break;
                }
            }
        }

        if (this.executeFrameStop) {
            //如果执行已经停下来,则开始执行
            this.executeNextFrame();
        }
    }
    getAfterFramesArrIndex(frameIndex: number) {
        return frameIndex - this.stateFrameIndex - 1;
    }
    /**真正执行下一帧(按顺序触发多个输入操作的实现),最后触发逻辑帧事件,返回是否执行完所有帧了(可能是执行前就完了,也可能是执行后完了)*/
    executeOneFrame(dt: number): boolean {
        const execFrameIndex = this.executeFrameIndex + 1;
        if (execFrameIndex > this.maxCanRenderFrameIndex) return true;
        this.executeFrameIndex = execFrameIndex;
        const arrIndex = this.getAfterFramesArrIndex(execFrameIndex);
        const frame = this.afterFrames[arrIndex];
        if (!frame) {
            console.error(`this.afterFrames[${arrIndex}]为空,frameIndex:${execFrameIndex}`);
        } else {
            const inputs = frame.connectionInputs;
            if (inputs) {
                for (let i = 0; i < inputs.length; i++) {
                    const inp = inputs[i];
                    for (let j = 0; j < inp.operates.length; j++) {
                        const op = inp.operates[j];
                        const fn = this.inputHandler['execInput_' + op.inputType];
                        if (fn) fn.call(this.inputHandler, inp.connectionId, op, dt, execFrameIndex);
                    }
                }
            }
        }

        this.onExecFrame?.call(this, dt, execFrameIndex);

        if (execFrameIndex >= this.maxCanRenderFrameIndex) {
            return true;
        } else {
            return false;
        }
    }
    /**开始执行下一帧(或者追上落后帧),并且自动根据情况执行之后的帧,执行完则自动停下来,设置 executeFrameStop=true*/
    executeNextFrame() {
        //处理帧信息
        if (this.executeOneFrame(this.renderFrameDt)) {
            //已经执行完所有帧了,等待服务器新消息
            this.executeFrameStop = true;
            return;
        }

        //未执行的帧数
        const unRenderFrameCount = this.stateFrameIndex + 1 + this.afterFrames.length - this.executeFrameIndex;
        //根据性能动态计算,下一帧要执行的间隔
        let frameInv = 0;
        if (unRenderFrameCount > 10) {
            //当缓存帧过多时,一次处理多个帧信息(卡着线程处理,会让有些物理引擎计算偏差?)
            for (let i = 0; i < unRenderFrameCount; i++) {
                if (this.executeOneFrame(this.renderFrameDt)) {
                    //已经执行完所有帧了,等待服务器新消息
                    this.executeFrameStop = true;
                    return;
                }
            }
            frameInv = 0;
        } else if (unRenderFrameCount > 3) {
            //相差一点,开始追帧(setTimetout 0,不卡死又能快速执行)
            frameInv = 0;
        } else {
            //正常速度
            frameInv = this.renderFrameInvMs;
        }
        if (this.executeFrameStop) return;

        /*
        //累计1k清理一波过期帧
        if (this.currFrameIndex > 1000) {
            this.frames.splice(0, this.currFrameIndex);
            this.currFrameIndex = 0;
        }
        */

        this.executeNextFrameTimerHD = setTimeout(
            this.executeNextFrameHandler,
            frameInv
        );
    }
    stopExecuteFrame() {
        this.executeFrameStop = true;
        clearTimeout(this.executeNextFrameTimerHD);
    }

    public sendInputFrames(ops: ConnectionInputOperate[]) {
        //正常输入实现,应当:即时操作推到一个数组,定时器发送然后清空
        this.connect.sendInpFrame({
            operates: ops
        });
    }
    public sendInputFrame(op: ConnectionInputOperate) {
        //正常输入实现,应当:即时操作推到一个数组,定时器发送然后清空
        this.connect.sendInpFrame({
            operates: [op]
        });
    }
}